#
# Copyright (C) 2008 Erik van Oosten
#
# MIT license.
#

require 'pathname'
require 'ftools'

class VirtualDomain
private
  # The Postfix database file with the virtual table located in the plugin top folder.
  VIRTUAL_FILE = Pathname.new(__FILE__).cleanpath + '../../../virtual'

  # The folder in which the postfix commands reside. Leave empty when they are in the path.
  POSTPATH = ""
  POSTMAP = POSTPATH + "postmap"
  POSTCONF = POSTPATH + "postconf"

  # The name of the @all@ group
  ALL_GROUP_NAME = "all"

public
  attr_accessor :virtual_addresses, :groups, :dirty

  # Tests whether the postfix configuration is compatible.
  #
  # Returns nil when all is okay, it returns a description
  # (in html formatting) of the problem otherwise.
  #
  def self.validate_postfix_config(domain)
    `#{POSTCONF} -n`
    return 'Could not execute postconf, is Postfix installed?<br/>Perhaps you need to set POSTPATH in #{__FILE__}?' if $?.exitstatus == 127
    return 'Was not allowed to execute postconf. Can not validate the configuration.' if $?.exitstatus == 126
    return 'Error #{$?.exitstatus} while executing postconf. Can not validate the configuration.' if $?.exitstatus != 0

    virtual_alias_domains = `#{POSTCONF} -h virtual_alias_domains`.chomp
    virtual_alias_maps = `#{POSTCONF} -h virtual_alias_maps`.chomp
    
    return "Make sure that the following lines are present in /etc/postfix/main.cf:<br/><tt>virtual_alias_domains = #{domain}<br/>virtual_alias_maps = hash:#{VIRTUAL_FILE}</tt>" unless virtual_alias_domains.include?(domain) && virtual_alias_maps.include?('hash:' + VIRTUAL_FILE)

    # All ok
    nil
  end

  def initialize(domain)
    @domain = domain
    @virtual_addresses = []
    @groups = []
    @dirty = false
    load
  end
  
  def load
    mapping_re = Regexp.new "^([a-zA-Z0-9_-][a-zA-Z0-9_\.\-]*)@#{@domain}\s+(.+)$"
    group_re = Regexp.new "# Groups: (.*)$"
    @virtual_addresses = []
    @groups = []
    if VIRTUAL_FILE.exist?
      IO.readlines(VIRTUAL_FILE).each do |line|
         _, groups = *group_re.match(line)
        @groups = groups.split(",") if groups
        _, user, redirect = *mapping_re.match(line)
        @virtual_addresses << VirtualAddress.new(user, redirect) if user && user != "postmaster"
      end
      # Remove the group redirects from the virtual addresses
      groups_addresses, @virtual_addresses = @virtual_addresses.partition {|vaddr| @groups.include? vaddr.user }
      # Make the users member of their groups
      va_hash = Hash.new
      @virtual_addresses.each {|vaddr| va_hash[vaddr.user] = vaddr }
      domain_email_re = Regexp.new "^([a-zA-Z0-9_-][a-zA-Z0-9_\.\-]*)@#{@domain}$"
      groups_addresses.each do |group_vaddr|
        group_vaddr.redirect.split(",").each do |email|
          _, user = *domain_email_re.match(email)
          va_hash[user].add_to_group(group_vaddr.user)
        end
      end
    end
    # Make sure group 'all' is present
    add_group ALL_GROUP_NAME
    @dirty = false
    # Sort for nice display, and return the list
    @virtual_addresses = @virtual_addresses.sort
    self
  end

  def save
    load unless @virtual_addresses
    backup_file_name = "#{VIRTUAL_FILE}.#{Time.new.strftime("%Y%m%d.%H%M%S")}"
    File.copy(VIRTUAL_FILE, backup_file_name) if VIRTUAL_FILE.exist? 
    File.open(VIRTUAL_FILE, "w") do |file|
      file.write "# This file was generated from the Radiant plugin EmailDirections.\n"
      file.write "# Groups: #{@groups.join(",")}\n"
      file.write "postmaster@#{@domain} postmaster\n"
      @virtual_addresses.each {|va| file.write "#{va.user}@#{@domain} #{va.redirect}\n"}
      @groups.each do |group|
        members = @virtual_addresses.find_all {|vaddr| vaddr.member_of?(group)}
        if !members.empty?
          member_emails = members.collect {|vaddr| "#{vaddr.user}@#{@domain}"}.join(",")
          file.write "#{group}@#{@domain} #{member_emails}\n"
        end
      end
    end
    @dirty = false
    nil
  end
  
  def activate
    `#{POSTMAP} #{VIRTUAL_FILE}`
    raise "Could not update postfix table" unless $?.exitstatus == 0
    @dirty = false
  end

  def add_user(vaddr)
    @virtual_addresses << vaddr
    @virtual_addresses.sort
  end

  def add_group(group)
    @groups << group unless (group.empty? || @groups.include?(group))
    nil
  end

  def remove_group(group)
    @groups.delete(group)
    nil
  end
  
end
